/*
 *
 * Written by NIIBE Yutaka
 *
 * Copyright (C) 2009 Free Software Initiative of Japan
 *
 * Distributed under the terms of GPLv3 (or later)
 */

/*
 * You need libusb to use this program.
 *
 * On Debian, type:
 * $ apt-get libusb-dev
 */

/*
 * $ gcc -o akiusb-chars akiusb-chars.c -lusb
 * $ sudo ./akiusb-chars
 */

#include <stdio.h>
#include <string.h>
#include <usb.h>

#include <poll.h>

#define USB_VENDOR  0x16c0
#define USB_PRODUCT 0x05dc
#define USB_MANUFACTURER_STR "fsij.org"
#define USB_PRODUCT_STR "Aki-USB"
#define USB_SERIALNUMBER_STR "0"

#define ENDPOINT     2

static int
compare_usb_string (usb_dev_handle *udev, int index, char *str)
{
  int len;
  char buf[256];

  if ((len = usb_get_string_simple (udev, index, buf, 256)) < 0)
    {
      perror ("USB_GET_STRING_SIMPLE failed\n");
      return -1;
    }
  
  return strncmp (buf, str, len);
}

#define USBLOWSPEED_BUFSIZE 8
char usbbuf[USBLOWSPEED_BUFSIZE];

#define AKIUSB_DRAW   0
#define AKIUSB_SETPTR 1
static int
send_cmd_setptr (usb_dev_handle *udev, unsigned char col)
{
  int r;

  usbbuf[0] = AKIUSB_SETPTR;
  usbbuf[1] = col;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 2, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

#define AKIUSB_CLRSCR 2
static int
send_cmd_clrscr (usb_dev_handle *udev)
{
  int r;

  usbbuf[0] = AKIUSB_CLRSCR;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 1, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

#define AKIUSB_VMODE 3
static int
send_cmd_vmode (usb_dev_handle *udev, unsigned char mode, unsigned char value)
{
  int r;

  usbbuf[0] = AKIUSB_VMODE;
  usbbuf[1] = mode;
  if (mode == 0)
    usbbuf[2] = value;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, (mode == 0) ? 3 : 2, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

#define AKIUSB_CHAR 4
static int
send_cmd_char (usb_dev_handle *udev, unsigned char c)
{
  int r;

  usbbuf[0] = AKIUSB_CHAR;
  usbbuf[1] = c;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 2, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

static int
draw (usb_dev_handle *udev, char *text)
{
  int i, r;

  for (i = 0; text[i] && i < 4; i++)
    if ((r = send_cmd_char (udev, text[i])) < 0)
      return r;

  return 0;
}

int
main (int argc, char *argv[])
{
  struct usb_bus *bus;
  struct usb_device *dev;
  usb_dev_handle *udev;
  int i;
  char *text;

  if (argc == 1)
    text = "GSoC";
  else if (argc == 2)
    text = argv[1];
  else
    {
      printf ("Usage: %s <text-to-display>\n", argv[0]);
      exit (1);
    }

  usb_init();

  usb_find_busses();
  usb_find_devices();

  for (bus = usb_busses; bus; bus = bus->next)
    for (dev = bus->devices; dev; dev = dev->next)
      {
	printf ("%04x:%04x\n", dev->descriptor.idVendor, dev->descriptor.idProduct);

	/* Check the vendor ID and product ID */
	if (dev->descriptor.idVendor == USB_VENDOR
	    && dev->descriptor.idProduct == USB_PRODUCT)
	  {
	    /* XXX: assert dev->descriptor.bNumConfigurations == 1 */

	    /* Check the interface */
	    for (i = 0; i < dev->config[0].bNumInterfaces; i++)
	      {
	  	/* XXX
		 * assert dev->config[0].interface[i].num_altsetting == 1
		 */
		
		/* Check the class */
	  	if (dev->config[0].interface[i].altsetting[0].bInterfaceClass
		    == 0xff) /* Vendor specific */
		  {
		    /* Ok, it looks like our product */
		    /* Check the strings */

		    udev = usb_open (dev);
		    if (!udev)
		      {
			perror ("USB_OPEN failed\n");
			return 1;
		      }

		    if (compare_usb_string (udev, dev->descriptor.iManufacturer,
					    USB_MANUFACTURER_STR) == 0
			&& compare_usb_string (udev, dev->descriptor.iProduct,
					       USB_PRODUCT_STR) == 0
			&& compare_usb_string (udev, dev->descriptor.iSerialNumber,
					       USB_SERIALNUMBER_STR) == 0)
		      goto found;

		    usb_close (udev);
		  }
	      }
	  }
      }

  puts ("No device found");
  return 1;


 found:

  printf ("interface %d\n", i);
  if (usb_set_configuration (udev, 1) < 0)
    {
      perror ("USB_SET_CONFIGURATION");
      /* Fall through */
    }

  if (usb_claim_interface (udev, i) < 0)
    {
      perror ("USB_CLAIM_INTERFACE");
      return 1;
    }

#define TTY_BUFSIZE 256
#define TIMEOUT 30

  if (send_cmd_vmode (udev, 0, 12) < 0)
    exit (1);

  if (send_cmd_clrscr (udev) < 0)
    exit (1);

  if (send_cmd_setptr (udev, 0) < 0)
    exit (1);

  if (draw (udev, text) < 0)
    exit (1);

  usb_close (udev);

  return 0;
}
